package com.ly.gateway.filter;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSON;
import com.ly.common.core.constants.CacheConstants;
import com.ly.common.core.constants.Constants;
import com.ly.common.core.constants.HttpConstants;
import com.ly.common.core.domain.R;
import com.ly.common.core.enums.ResultCode;
import com.ly.common.core.enums.UserIdentity;
import com.ly.common.core.domain.LoginUser;
import com.ly.common.core.utils.JwtUtils;
import com.ly.common.core.utils.ThreadLocalUtil;
import com.ly.common.redis.service.RedisService;
import com.ly.gateway.properties.IgnoreWhiteProperties;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.CollectionUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.List;
@Slf4j
@Component
//完成身份认证  GlobalFilter全局过滤器:网关,有客户端发给服务器经过网关,网关转发给具体服务
public class AuthFilter implements GlobalFilter, Ordered {
    // 排除过滤的 uri ⽩名单地址，在nacos⾃⾏添加
    @Autowired
    private IgnoreWhiteProperties ignoreWhite;

    @Value("${jwt.secret:default_temp_secret}")
    private String secret;

    @Autowired
    private RedisService redisService;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

        //获取http请求
        ServerHttpRequest request = exchange.getRequest();
        String url = request.getURI().getPath();//请求接口地址 登录接口是否需要身份认证? 否
        // 跳过不需要验证的路径 接口白名单,通过白名单的接口均不需要进行身份验证
        if (matches(url, ignoreWhite.getWhites())) {//判断当前接口如果在白名单中则不需要身份认证
            return chain.filter(exchange);          //ignoreWhite.getWhites():拿到nacos上配置接口地址的白名单
        }

        //执行到这里,说明接口不在白名单上,接着进行身份认证
        //通过token进行身份认证,客户端会把token放在请求头中,需要把token获取出来
        //从http请求头中获取token

        String token = getToken(request);
        if (StrUtil.isEmpty(token)) {
            //为什么不用 throw new RuntimeException
            return unauthorizedResponse(exchange, "令牌不能为空");
        }

        Claims claims;

        try {
// Payload是JWT中最重要的部分，因为它包含了令牌携带的实际信息，这些信息可以在不接触数据库的情况下验证用户身份和权限
// 在JWT(JSON Web Token)中，payload是指令牌的中间部分，包含了令牌携带的实际数据(也称为"声明"claims)。

            claims = JwtUtils.parseToken(token, secret); //获取令牌中信息 解析payload中信息 存储着用户的唯一表示信息
            if (claims == null) {
                //soringcloud gateway基于webflux
                return unauthorizedResponse(exchange, "令牌已过期或验证不正确！");
            }
        } catch (Exception e) {
            return unauthorizedResponse(exchange, "令牌已过期或验证不正确！");
        }
//        String key ="logintoken:" + sysUser.getUserId();这个方法存储的key,可以这样取值
//        String userId = JwtUtils.getUserId(claims);
//        boolean isLogin = redisService.hasKey(getTokenKey(userId));

        //控制token有效时间,判断redis存储的敏感信息到期,是否存在
        String userKey = JwtUtils.getUserKey(claims); //获取jwt中的key
        boolean isLogin = redisService.hasKey(getTokenKey(userKey));

        if (!isLogin) {
            return unauthorizedResponse(exchange, "登录状态已过期");
        }

        String userId = JwtUtils.getUserId(claims); //判断jwt中的信息是否完整
        if (StrUtil.isEmpty(userId)) {
            return unauthorizedResponse(exchange, "令牌验证失败");
        }

        //token正确,并且没有过期
        //判断redis存储的关于用户身份认证信息是否正确
        //判断当前请求 请求的时C端功能(只有C端用户可以请求)     还是B端功能(只有管理员可以请求)

        //拿出存储信息
        //url.contains,前缀如果是Steam,访问system接口
        //             前缀如果是friend,访问friend接口
        LoginUser user = redisService.getCacheObject(getTokenKey(userKey), LoginUser.class);
         if (url.contains(HttpConstants.SYSTEM_URL_PREFIX) &&
                !UserIdentity.ADMIN.getValue().equals(user.getIdentity())) {
            return unauthorizedResponse(exchange, "令牌验证失败");
        }

        if (url.contains(HttpConstants.FRIEND_URL_PREFIX) &&
                !UserIdentity.ORDINARY.getValue().equals(user.getIdentity())) {
            return unauthorizedResponse(exchange, "令牌验证失败");
        }
//        将当前用户的ID（userId）存储到线程本地变量（ThreadLocal）中
//        并以常量Constants.USER_ID作为键名。
//        如果写在这里,存在线程隔离 common和gateway是两个微服务,
//        所以换到TokenInterceptor中了
        //ThreadLocalUtil.set(Constants.USER_ID,userId);

        //确定身份认证之后,进行时间延长

        return chain.filter(exchange);
    }

    /**
     * 查找指定url是否匹配指定匹配规则链表中的任意⼀个字符串
     *
     * @param url         指定url
     * @param patternList 需要检查的匹配规则链表
     * @return 是否匹配
     */
    private boolean matches(String url, List<String> patternList) {
//接口匹配
        if (StrUtil.isEmpty(url) || CollectionUtils.isEmpty(patternList)) {
            return false;
        }
//接口地址如果和白名单其中一个地址匹配就返回true
// 如果遍历完白名单所有地址都没有匹配返回false
        for (String pattern : patternList) {
            if (isMatch(pattern, url)) {
                //匹配上了
                return true;
            }
        }

        return false;
    }

    /**
     * 判断 url是否与规则匹配
     * 匹配规则中：
     * pattern 中可以写一些特殊字符
     * ? 表示单个字符;
     * * 表示⼀层路径内的任意字符串，不可跨层级;
     * ** 表示任意层路径;
     *
     * @param pattern 匹配规则
     * @param url     需要匹配的url
     * @return 是否匹配
     */
    private boolean isMatch(String pattern, String url) {
        AntPathMatcher matcher = new AntPathMatcher();
        return matcher.match(pattern, url);
    }

    /**
     * 获取缓存key
     */
    private String getTokenKey(String token) {
        //往redis中存储值时,就是带了CacheConstants.LOGIN_TOKEN_KEY 的前缀
        return CacheConstants.LOGIN_TOKEN_KEY + token;
    }

    /**
     * 从请求头中获取请求token
     */
    private String getToken(ServerHttpRequest request) {
        String token = request.getHeaders().getFirst(HttpConstants.AUTHENTICATION);
        // 如果前端设置了令牌前缀，则裁剪掉前缀
        if (StrUtil.isNotEmpty(token) && token.startsWith(HttpConstants.PREFIX)) {
            token = token.replaceFirst(HttpConstants.PREFIX, StrUtil.EMPTY);
        }
        return token;
    }

    private Mono<Void> unauthorizedResponse(ServerWebExchange exchange, String
            msg) {
        log.error("[鉴权异常处理]请求路径:{}", exchange.getRequest().getPath());
        return webFluxResponseWriter(exchange.getResponse(), msg, ResultCode.FAILED_UNAUTHORIZED.getCode());
    }

    //拼装webflux模型响应
    //用于返回一个 JSON 格式的错误响应
    private Mono<Void> webFluxResponseWriter(ServerHttpResponse response, String msg, int code) {
        response.setStatusCode(HttpStatus.OK);
        response.getHeaders().add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
        R<?> result = R.fail(code, msg);
        DataBuffer dataBuffer = response.bufferFactory().wrap(JSON.toJSONString(result).getBytes());
        return response.writeWith(Mono.just(dataBuffer));
    }

    //定义执行顺序,值越小,过滤器越先被执行
    @Override
    public int getOrder() {
        return -200;
    }
    public static void main(String[] args) {
        AuthFilter authFilter = new AuthFilter();
// 测试 ?
// ? 表示单个字符;
//        String pattern = "/sys/?bc";
//        System.out.println(authFilter.isMatch(pattern,"/sys/abc"));
//        System.out.println(authFilter.isMatch(pattern,"/sys/cbc"));
//        System.out.println(authFilter.isMatch(pattern,"/sys/acbc"));
//        System.out.println(authFilter.isMatch(pattern,"/sdsa/abc"));
//        System.out.println(authFilter.isMatch(pattern,"/sys/abcw"));
//     测试*  一个斜杠就是一个层级
//        * 表示⼀层路径内的任意字符串，不可跨层级;
//     String pattern = "/sys/*/bc";
//     System.out.println(authFilter.isMatch(pattern,"/sys/a/bc"));
//     System.out.println(authFilter.isMatch(pattern,"/sys/sdasdsadsad/bc"));
//     System.out.println(authFilter.isMatch(pattern,"/sys/a/b/bc"));
//     System.out.println(authFilter.isMatch(pattern,"/a/b/bc"));
//     System.out.println(authFilter.isMatch(pattern,"/sys/a/b/"));


//     测试**
//     ** 表示任意层路径;

//     String pattern = "/sys/**/bc";
//     System.out.println(authFilter.isMatch(pattern, "/sys/a/bc"));
//     System.out.println(authFilter.isMatch(pattern, "/sys/sdasdsadsad/bc"));
//     System.out.println(authFilter.isMatch(pattern, "/sys/a/b/bc"));
//     System.out.println(authFilter.isMatch(pattern, "/sys/a/b/s/23/432/fdsf///bc"));
//     System.out.println(authFilter.isMatch(pattern, "/a/b/s/23/432/fdsf///bc"));
//     System.out.println(authFilter.isMatch(pattern, "/sys/a/b/s/23/432/fdsf///"));
 }
}